package cn.demoncat.util.redis.util;

import cn.demoncat.util.lang.NumberUtil;
import cn.demoncat.util.redis.config.RedisIsolator;
import cn.demoncat.util.redis.entity.RedisLock;
import cn.demoncat.util.redis.entity.RedisSeq;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * Redis序列工具
 *
 * 注：为了跟踪项目中使用了哪些序列，必须使用RedisSeq对象来声明序列的缓存常量
 *
 * @author 延晓磊
 *
 * @since 2018年7月13日
 */
@Component
public class RedisSeqUtil {

	@Resource
	private RedisNumUtil redisNumUtil;
	@Resource
	private RedisIsolator redisIsolator;

	// 序列锁：数据不存在时加锁重置
	private static final RedisLock LOCK = new RedisLock("SEQ:");


	/**
	 * 获取序列ID
	 *
	 * @param seq			序列常量
	 * @param dataGetter	末尾数据获取器：prefix -> CollectionUtil.first(orderInfoMapper.selectList(new Query<OrderInfo>().select("id").likeLeft("id",prefix).descId().limit(1)));
	 * @param idGetter		数据ID获取器
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2021年02月13日
	 */
	public <O>String get(RedisSeq seq, Function<String, O> dataGetter, Function<O,String> idGetter){
		// 前缀
		String prefix = seq.getCurrPrefix();
		// 序列
		Long num = nextId(seq.getKey() + prefix, seq.getExp(), () -> {
			// 查询最后一个数据
			O last = dataGetter.apply(prefix);
			if (last == null) {
				return 1L;
			}else{
				// 通过最后一条数据，获取序列
				return seq.getSeq(idGetter.apply(last));
			}
		});
		// 格式化
		return prefix + (StringUtils.isBlank(seq.getFormat()) ? num : NumberUtil.format(num, seq.getFormat()));
	}

	/**
	 * 获取序列ID
	 *
	 * @param key	序列缓存键
	 * @param exp	序列缓存时间
	 * @param init	初始化器：获取最新序列，如 SELECT id FROM order_info WHERE id LIKE 'prefix%' ORDER BY id DESC LIMIT 1
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年1月26日
	 */
	public Long nextId(String key, Integer exp, Supplier<Long> init) {
		Long r;
		if (init == null) {
			// 普通递增（如果缓存丢失，将从1开始）
			r = redisNumUtil.incr(key);
		}else{
			// 容错递增（如果递增值为1，通过初始化器确认/补偿）
			r = redisNumUtil.incr(key, LOCK.build(key), init);
		}
		if (r == 1) {
			// 首次递增，设置缓存过期时间，以便自动清理
			redisNumUtil.getRedisTemplate().expire(redisIsolator.getKey(key), exp, TimeUnit.SECONDS);
		}
		return r;
	}


}
